// ============================================================================
// JigWorm
// "The worm is the spice, the spice is the worm" (Dune)
// This is where the basic movements for the worm are computed and where
// it's positional data is stored. The worm also know how to draw itself.
// Normally I wouldn't allow an object to draw itself because it binds
// it to a particualr graphics system. However as Java comes with
// a standard graphical library it's not too bad.
//
// CLH April-June 1998
// ============================================================================

package com.chrishobson.jiggle;

import java.awt.*;

public class JigWorm {

  // ==========================================================================
  // The maximum allowable length (in segments) for any worm.
  // ==========================================================================
  public static final int s_max_length = 200;

  // ==========================================================================
  // Creates a new Jiggle Worm with its head at the specified x,y.
  // The tail of the worm is at x-1, y so worms have an initial
  // length of 1 segment. The colour that the worm is to be drawn is
  // is also specified
  // ==========================================================================
  public JigWorm(int x, int y, Color c) {
    m_colour = c;
    m_x[0] = x;
    m_y[0] = y;
    m_x[1] = x - 1;
    m_y[1] = y;
  }

  // ==========================================================================
  // Creates a new Jiggle Worm from a String.
  // The string is assumed to have been created by the toString method.
  // ==========================================================================
  public JigWorm(String s) {
    // Do the work by using the available JigParser constructor
    this(new JigParser(s));
  }

  // ==========================================================================
  // Creates a new Jiggle Worm from a String stored in a parser.
  // This allows worms to be extracted from strings which might contain
  // other extra data where a worm is just a small part of the whole string.
  // ==========================================================================
  public JigWorm(JigParser p) {
    // Length comes first
    m_length = p.next_int();
    // Parse out the colour
    m_colour = new Color(p.next_int(), p.next_int(), p.next_int());

    // And the XY positions
    for (int i = 0; i <= m_length; ++i) {
      m_x[i] = p.next_int();
      m_y[i] = p.next_int();
    }
  }

  // ==========================================================================
  // Sets the worms colour, allows it to be changed after creation.
  // ==========================================================================
  public void set_colour(Color c) {
    m_colour = c;
  }

  // ==========================================================================
  // Gets the worms colour.
  // ==========================================================================
  public Color get_colour() {
    return m_colour;
  }

  // ==========================================================================
  // Set the worm as dead so it undraws itself next draw.
  // ==========================================================================
  public void set_dead() {
    m_last = DRAW_DEAD;
  }

  // ==========================================================================
  // Query if the worm is dead.
  // ==========================================================================
  public boolean is_dead() {
    return m_last == DRAW_DEAD ? true : false;
  }

  // ==========================================================================
  // Moves the worm forward 1 in the current direction of travel.
  // ==========================================================================
  public void move_forward() {
    // Move in the current direction
    move(m_x[0] - m_x[1], m_y[0] - m_y[1]);
  }

  // ==========================================================================
  // Moves the worm right 1 relative to the current direction of travel.
  // ==========================================================================
  public void move_right() {
    // The current direction vector is given by x[0]-x[1],y[0]-y[1]
    // to turn left the x and y components need to be swapped
    // and THEN the (new) x component is negated
    // Initial dir 1) x[0]-x[1], y[0]-y[1]
    // swap 2) y[0]-y[1], x[0]-x[1]
    // negate 3) y[1]-y[0], x[0]-x[1]

    move(m_y[1] - m_y[0], m_x[0] - m_x[1]);
  }

  // ==========================================================================
  // Moves the worm left 1 relative to the current direction of travel.
  // ==========================================================================
  public void move_left() {
    // The current direction vector is given by x[0]-x[1],y[0]-y[1]
    // to turn right the x and y components need to be swapped
    // and THEN the (new) y component is negated
    // Initial dir 1) x[0]-x[1], y[0]-y[1]
    // swap 2) y[0]-y[1], x[0]-x[1]
    // negate 3) y[0]-y[1], x[1]-x[0]

    move(m_y[0] - m_y[1], m_x[1] - m_x[0]);
  }

  // ==========================================================================
  // Grows the worm by 1 segment which is added to the front of the worm.
  // If the worm is not already at its maximum size then grow it by
  // moving the head forward one space whilst keeping the tail where
  // it is. If the worm is at its maximum size already then no action occurs
  // occurs.
  // ==========================================================================
  public void grow() {
    if (m_length < s_max_length) {
      int new_tail = m_length + 1;
      // First duplicate the position of the tail, new to bubble down
      // two positions so that the old tail remains available for
      // the undrawing
      m_x[new_tail + 1] = m_x[new_tail];
      m_x[new_tail] = m_x[m_length];
      m_y[new_tail + 1] = m_y[new_tail];
      m_y[new_tail] = m_y[m_length];
      // The tail is safely stored so move the worm forward to
      // create the effect of growth.
      move_forward();
      // Now update the length
      m_length = new_tail;
      // Update so next draws the head from the l/r/f move
      // and also the extra block added by the grow.
      m_last = DRAW_GROW;
    } else {
      System.out.println("Worm is at max length of " + s_max_length);
    }
  }

  // ==========================================================================
  // Draws the worm as a series of square blocks
  // ==========================================================================
  public void draw(Graphics g, Color back, int border, int square, boolean face) {
    // If face is true draw a face to the worm
    if (m_last == DRAW_DEAD) {
      g.setColor(back);
      for (int i = 0; i <= m_length; ++i) {
        draw_square(g, border, square, m_x[i], m_y[i]);
      }
    } else {
      g.setColor(m_colour);
      // System.out.println("Head at " + m_x[0] + " " + m_y[0]);
      if (m_last == DRAW_ALL) {
        // Draw all worm if not yet drawn
        for (int i = 0; i <= m_length; ++i) {
          draw_square(g, border, square, m_x[i], m_y[i]);
        }
      } else {
        // Always draw the head
        draw_square(g, border, square, m_x[0], m_y[0]);
        if (face == true) {
          g.setColor(Color.yellow);
          draw_square(g, border, square, m_x[0], m_y[0]);
          g.setColor(m_colour);
        }
        if (m_last == DRAW_GROW || face == true) {
          // If grown need to draw 1 as well
          draw_square(g, border, square, m_x[1], m_y[1]);
          // And if both grown and face we must draw square 2 to remove
          // the old face
          if (m_last == DRAW_GROW && face == true) {
            draw_square(g, border, square, m_x[2], m_y[2]);
          }
        }
        // Always undraw the old tail position
        g.setColor(back);
        draw_square(g, border, square, m_x[m_length + 1], m_y[m_length + 1]);
      }
      m_last = DRAW_MOVE;
    }
  }

  // ==========================================================================
  // Static method which draws the blocks for worms
  // ==========================================================================
  public static void draw_square(Graphics g, int border, int square, int x,
      int y) {
    int x_l = border + x * square;
    int y_l = border + y * square;

    g.fillRect(x_l, y_l, square, square);
  }

  // ==========================================================================
  // Sets the 'Point' to the position of the worms head.
  // ==========================================================================
  public final void get_head(Point p) {
    p.x = m_x[0];
    p.y = m_y[0];
  }

  // ==========================================================================
  // Sets the Point to the *previous* position of the tail.
  // ==========================================================================
  public final void get_last_tail(Point p) {
    p.x = m_x[m_length + 1];
    p.y = m_y[m_length + 1];
  }

  // ==========================================================================
  // Sets the Point to the end of specified segment.
  // ==========================================================================
  public final void get_pos(int i, Point p) {
    p.x = m_x[i];
    p.y = m_y[i];
  }

  // ==========================================================================
  // Returns number of segments in the worm.
  // ==========================================================================
  public final int get_length() {
    return m_length;
  }

  // ==========================================================================
  // Converts the Worm to a string representation.
  // ==========================================================================
  public String toString() {
    // The string will be approximately (in bytes)
    // 4 for the number of segements +
    // 4 * 3 for colours = 12 +
    // (m_length + 1) * 8 for the coordinates
    // So allocate a buffer which is close to the correct size
    // to avoid unecessary reallocation

    StringBuffer b = new StringBuffer(16 + (m_length + 1) * 8);
    add_buffer(b);
    return new String(b);
  }

  // ==========================================================================
  // Converts the Worm to a string representation appended to the buffer
  // ==========================================================================
  public void add_buffer(StringBuffer b) {
    b.append("L").append(m_length);
    b.append("R").append(m_colour.getRed());
    b.append("G").append(m_colour.getGreen());
    b.append("B").append(m_colour.getBlue());
    for (int i = 0; i <= m_length; ++i) {
      b.append("X").append(m_x[i]).append("Y").append(m_y[i]);
    }
  }

  // ======================================================================
  // Private methods and data below here
  // ======================================================================

  // ==========================================================================
  // Bubble the move arrays down so that the current tail position
  // drops off the end of the known moves but still remains in
  // the known posiitons so that it can use accessed for undrawing
  // by using index m_length + 1. The move arrays are correctly sized
  // to allow for this.
  // ==========================================================================
  private void move(int deltax, int deltay) {
    int p;
    for (int i = m_length + 1; i > 0; i = p) {
      p = i - 1;
      m_x[i] = m_x[p];
      m_y[i] = m_y[p];
    }

    // Update the head position by moving it by the delta values
    m_x[0] += deltax;
    m_y[0] += deltay;
  }

  // ==========================================================================
  // Prints out the worm, for debugging only
  // ==========================================================================
  @SuppressWarnings("unused")
  private void print() {
    System.out.println("Length " + m_length);
    for (int i = 0; i <= m_length; ++i) {
      System.out.println(m_x[i] + "\t" + m_y[i]);
    }
  }

  // The worms colour, used for drawing.
  private Color m_colour;

  // The current length of the worm.
  // Length is defined to be the array index of the last point and
  // so length defines the number of segments in the worm so a length
  // of 1 occupies 2 array slots, 2 occupies 3 slots etc.
  //
  private int m_length = 1;

  // Enumerated values which m_last can take

  private final int DRAW_ALL = 0;
  private final int DRAW_MOVE = 1;
  private final int DRAW_GROW = 2;
  private final int DRAW_DEAD = 3;

  // Stores the last move type so efficient drawing can occur.
  private int m_last = DRAW_ALL;

  // The X/Y position arrays.
  private int m_x[] = new int[s_max_length + 2];
  private int m_y[] = new int[s_max_length + 2];
}
